//==============================================================================
//
//          DRegEx - the regular expression matcher in the ofc-library
//
//               Copyright (C) 2003  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2008-08-03 18:31:55 $ $Revision: 1.18 $
//
//==============================================================================

#include "ofc/DRegEx.h"

#include "ofc/warning.h"

#include "ofc/DText.h"
#include "ofc/DData.h"
#include "ofc/DInt.h"

#include <stdlib.h>
#include <string.h>

#if _INTERFACE_

#include "ofc/config.h"

#if defined(HAVE_REGEX_H) && (defined(HAVE_RE_MATCH) || defined(HAVE_LIBREGEX))
#define HAVE_DREGEX (1)
#elif defined(HAVE_GNU_REGEX_H) && defined(HAVE_LIBGNUREGEX)
#define HAVE_DREGEX (1)
#endif


#ifdef HAVE_DREGEX

#if defined(HAVE_GNU_REGEX_H)
#include "gnu/regex.h"
#else
#include "regex.h"
#endif

#include "ofc/DArray.h"


#define DRE_NO_MATCH (-1)
#define DRE_ERROR    (-2)

//
// The RegEx class implements methods for using regular expressions. After
// compiling a regular expressing, it can be used to match and search strings.
// <pre>
//
//     This class uses the Extended POSIX syntax:
//      .   Match any char (incl. newline)   *         Match zero or more
//      +   Match one or more                ?         Match zero or one
//      {c} Match exactly c times            {min,max} Match min..max times
//      \|   Match alternatives               []        Match one in the list
//      [^] Match any except in list         [::]      Match a class in a list
//      ()  Group or subexpression
//      ^   Match begin of line              $         Match end of line
//
//     Classes: alnum, alpha, blank, cntrl, digit, graph, lower, print, punct, 
//              space, upper, xdigit
//
//     Examples: 
//           (aab\|zef)   matches aab or zef
//           aa(b\|z)ef   matches aabef or aazef
//           [a-c]       matches a or b or c
//           []de]       matches ] or d or e
//           [^ad]       match any except a and b
//           [[:alpha:]] matches letters (*NOTE*: use ccompile and not icompile)
//           [.*]        matches . and *
// </pre>
//
// @example
// #include <stdio.h>
// #include "ofc/DRegEx.h"
// #include "ofc/DText.h"
// 
// int main(int argc, char *argv[])
// {
//   DRegEx *expr1 = [DRegEx alloc];
//   DRegEx *expr2 = [DRegEx new  ];
//   
//   int     matched;
//   int     index;
// 
//   [expr1 init :"a+b"];                 // Init with a regular expression
// 
//   matched = [expr1 match :"aaba"];     // Match a string with the regular expression
//   if (matched == DRE_NO_MATCH)
//     printf("String1 did not match the regular expression.\n");
//   else
//     printf("The first %d characters of string1 matched the regular expression.\n", matched);
// 
//   matched = [expr1 match :"baaba"];    // Match another string with the regular expression
//   if (matched == DRE_NO_MATCH)
//     printf("String2 did not match the regular expression.\n");
//   else
//     printf("The first %d characters of the string2 matched the regular expression.\n", matched);
// 
//   matched = [expr1 match :"baab" :1];  // Match a string with offset with the regular expression
//   if (matched == DRE_NO_MATCH)
//     printf("String3 did not match the regular expression.\n");
//   else
//     printf("The offseted %d characters of the string3 matched the regular expression.\n", matched);
// 
//   index = [expr1 search :"zyxab"];     // Search for the regular expression in the string
//   if (index == DRE_NO_MATCH)
//     printf("The regular expression could not be found in string4.\n");
//   else
//     printf("The regular expression started at %d in the string.\n", index);
// 
// 
//   [expr2 icompile :"a(b+)a"];          // Compile a regular expression, case insensitive
// 
//   matched = [expr2 match :"abbba"];    // Match the string with the regular expression
//   if (matched == DRE_NO_MATCH)
//     printf("String5 did not match the regular expression.\n");
//   else
//   {
//     DArray *matches = [expr2 matches :"abbba"]; // Get the matches from last match
// 
//     if (matches != nil)
//     {
//       for (index = 0; index < [matches length]; index++)
//       {
//         DText *str = [matches get :index];
// 
//         printf("Match %d:%s.\n", index, [str cstring]);
//       }
//       [matches free];
//     }
//   }
// 
//   [expr1 free];                        // Cleanup
//   [expr2 free];
// 
//   return 0;
// }
// 

@interface DRegEx : DObject
{
@private
  regex_t             _pattern;  // the compiled pattern
  struct re_registers _regs;     // the match registers
  int                 _result;   // the result of the last match/search
  size_t              _length;   // the length of the last string
}

#endif
#endif



#ifdef HAVE_DREGEX

@implementation DRegEx


#if _PRIVATE_

static unsigned char caseFold[256];
static BOOL caseInit = NO;

static void initCaseFold()
{
  int c;
  
  caseInit = YES;
  
  for (c = 0; c < sizeof(caseFold)/sizeof(char); c++)
    caseFold[c] = c;
  
  for (c = 'A'; c <= 'Z'; c++)
    caseFold[c] = c - ('A' - 'a');
}

#endif


//// Constructors

//
// Initialise to an empty expression
//
// @return the object
//

- (DRegEx *) init
{
  [super init];

  _pattern.buffer    = NULL;
  _pattern.allocated = 0L;
  _pattern.fastmap   = NULL;
  _pattern.translate = NULL;
  
  _regs.num_regs     = 0;
  _regs.start        = NULL;
  _regs.end          = NULL;
  
  return self;
}

//
// Initialise with a pattern
//
// @param pattern  the (case sensitive) c-string pattern
//
// @return the object
//

- (DRegEx *) init :(const char *) pattern
{
  [self init];

  [self ccompile :pattern];

  return self;
}


//// Copy related methods

//
// Do a shallow copy of the object (not implemented)
//
// @return the object
//
- shallowCopy
{
  WARNING(DW_METHOD_NOT_IMPL, "shallowCopy");
  
  return [super shallowCopy];
}


//// Destructor

//
// Free the expression
//
// @return the object
//

- free
{
  _pattern.translate = NULL;

  regfree(&_pattern);

  // Well, somebody has to do it..
  if (_regs.start != NULL)
    free(_regs.start);
  if (_regs.end   != NULL)
    free(_regs.end);
  
  [super free];
  
  return self;
}


//// Compile methods

//
// Compile a case sensitive pattern (POSIX Extended syntax)
// 
// @param pattern  the c-string pattern
//
// @return success
//

- (BOOL) ccompile :(const char *) pattern
{
  BOOL  ok = NO;
    
  if (pattern != NULL)
  {
    const char *msg;
    
    re_syntax_options  = RE_SYNTAX_POSIX_EXTENDED;
    _pattern.translate = NULL;
    
    msg = re_compile_pattern(pattern, strlen(pattern), &_pattern);

    if (_regs.num_regs > 0)
    {
      _pattern.regs_allocated = REGS_REALLOCATE;
    }

    if (msg != NULL)
    {
      WARNING(DW_INVALID_PATTERN, msg);
    }
    else
      ok = YES;
  }
  
  return ok;
}

//
// Compile a case insensitive pattern (POSIX Extended syntax)
// 
// @param pattern  the c-string pattern
//
// @return success
//

- (BOOL) icompile :(const char *) pattern
{
  BOOL  ok = NO;
    
  if (pattern != NULL)
  {
    const char *msg;
    
    if (!caseInit)
      initCaseFold();
      
    re_syntax_options  = RE_SYNTAX_POSIX_EXTENDED;
    _pattern.translate = caseFold;
    
    msg = re_compile_pattern(pattern, strlen(pattern), &_pattern);

    if (_regs.num_regs > 0)
    {
      _pattern.regs_allocated = REGS_REALLOCATE;
    }

    if (msg != NULL)
    {
      WARNING(DW_INVALID_PATTERN, msg);
    }
    else
      ok = YES;
  }
  
  return ok;
}


//// Matching and searching methods

//
// Match the string for the pattern
//
// @param str      the string to be matched
//
// @return the number of characters matched (or DRE_NO_MATCH, DRE_ERROR)
//

- (int) match :(const char *) str
{
  _result = DRE_NO_MATCH;
  
  if (str != NULL) 
  {
    _length = strlen(str);
    
    if (_length > 0)
    {
      _result = re_match(&_pattern, str, _length, 0, &_regs);
    }
  }
  return _result;
}

//
// Match the string with an offset for the pattern
// 
// @param str    the string to be matched
// @param from   the start in the string for the matching
// 
// @return the number of characters matched (or DRE_NO_MATCH, DRE_ERROR)
// 

- (int) match :(const char *) str :(int) from
{
  _result = DRE_NO_MATCH;
  
  if (str != NULL)
  {
    _length = strlen(str);
    
    if ((_length > 0) && (from < _length))
    {
      _result = re_match(&_pattern, str, _length, from, &_regs);
    }
  }
  
  return _result;
}

//
// Match the data string with an offset for the pattern
// 
// @param data     the data string to be matched
// @param length   the length of the data string
// @param from     the start in the data string for the matching
// 
// @return the number of characters matched (or DRE_NO_MATCH, DRE_ERROR)
// 

- (int) match :(const unsigned char *) data :(int) length :(int) from
{
  _result = DRE_NO_MATCH;
  
  if (data != NULL)
  {
    _length = length;
    
    if ((_length > 0) && (from < _length))
    {
      _result = re_match(&_pattern, (const char *) data, _length, from, &_regs);
    }
  }
  
  return _result;
}

//
// Search in a string for the pattern
//
// @param str      the string to be searched
// 
// @return the offset in the string where the pattern matched (or DRE_NO_MATCH, DRE_ERROR)
// 

- (int) search :(const char *) str
{
  _result = DRE_NO_MATCH;
  
  if (str != NULL)
  {
    _length = strlen(str);
    
    if (_length > 0)
    {
      _result = re_search(&_pattern, str, _length, 0, _length, &_regs);
    }
  }
  
  return _result;
}

//
// Search in a range of a string for the pattern
// 
// @param str      the string to be searched
// @param from     the start of the range for searching
// @param to       the end of the range for searching
// 
// @return the offset in the string where the pattern matched (or DRE_NO_MATCH, DRE_ERROR)
// 

- (int) search :(const char *) str :(int) from :(int) to
{
  _result = DRE_NO_MATCH;
  
  if (str != NULL)
  {
    _length = strlen(str);
    
    if ((_length > 0) && (from < _length) && (to < _length))
    {
      _result = re_search(&_pattern, str, _length, from, to, &_regs);
    }
  }
  
  return _result;
}

//
// Search in a data string for the pattern
//
// @param data     the data string to be searched
// @param length   the length of the data string
// 
// @return the offset in the data string where the pattern matched (or DRE_NO_MATCH, DRE_ERROR)
// 

- (int) search :(const unsigned char *) data :(int) length
{
  _result = DRE_NO_MATCH;
  
  if (data != NULL)
  {
    _length = length;
    
    if (_length > 0)
    {
      _result = re_search(&_pattern, (const char *) data, _length, 0, _length, &_regs);
    }
  }
  
  return _result;
}

//
// Search in a range of a data string for the pattern
// 
// @param data     the data string to be searched
// @param length   the length of the data string
// @param from     the start of the range for searching
// @param to       the end of the range for searching
// 
// @return the offset in the data string where the pattern matched (or DRE_NO_MATCH, DRE_ERROR)
// 

- (int) search :(const unsigned char *) str :(int) length :(int) from :(int) to
{
  _result = DRE_NO_MATCH;
  
  if (str != NULL)
  {
    _length = length;
    
    if ((_length > 0) && (from < _length) && (to < _length))
    {
      _result = re_search(&_pattern, (const char *) str, _length, from, to, &_regs);
    }
  }
  
  return _result;
}


//// Result methods

//
// Return the subexpression indices from the last match/search. The indices
// are returned in a DArray with (new) DInt objects. The sequence in the array
// is: index 0 = start of whole matched string, index 1 = end of whole matched
// string, index 2 = start of first subexpression, index 3 = end of first 
// subexpression, and so on. Warning: if a group matches more than once as a
// result of a repetition operator, only the last match is stored in the indices.
// 
// @return a (new) array (or nil for no match)
// 

- (DArray *) indices
{
  DArray *indices = nil;
  
  if (_result >= 0)
  {
    int i,r;
    
    int len = (1 + _pattern.re_nsub);
    
    indices = [DArray alloc]; [indices init :2 * len];

    i = 0;
    for (r = 0; r < len; r++)
    {
      DInt *index;
      
      index = [DInt alloc]; [index init :_regs.start[r]];
      [indices set :i++ :index];
      index = [DInt alloc]; [index init :_regs.end[r]-1];
      [indices set :i++ :index];
    }
  }
  
  return indices;
}

//
// Return the matches from the last match/search. The matches are
// returned in a (new) DArray with (new) DText objects.
// 
// @param str      the string that was matched/searched
// 
// @return a (new) array (or nil for no match)
// 

- (DArray *) matches :(const char *) str
{
  DArray *subs = nil;
  
  if ((str != NULL) && (strlen(str) == _length))
  {
    if (_result >= 0)
    {
      DText *text;
      int    r;
      int    len = (1 + _pattern.re_nsub);
      
      text = [DText alloc];  [text init :str];
      subs = [DArray alloc]; [subs init :len];
    
      for (r = 0; r < len; r++)
      {
        if ((_regs.start[r] >= 0) && (_regs.end[r] >= 0))
        {
          [subs set :r :[text get :_regs.start[r] :_regs.end[r]-1]];
        }
        else
        {
          [subs set :r :[[DText alloc] init]];
        }
      }
      
      [text free];
    }
  }
  else
    WARNING(DW_INVALID_ARG, "str");
  
  return subs;
}

//
// Return the matches from the last match/search. The matches
// are returned in a (new) DArray with (new) DData objects.
// 
// @param data     the data string that was matched/searched
// @param length   the length of the data string
// 
// @return a (new) array (or nil for no match)
// 

- (DArray *) matches :(const unsigned char *) data :(int) length
{
  DArray *subs = nil;
  
  if ((data != NULL) && (length == _length))
  {
    if (_result > 0)
    {
      DData *text;
      int    r;
      int    nr = (1 + _pattern.re_nsub);
    
      text = [DData alloc];  [text init :data :length];
      subs = [DArray alloc]; [subs init :nr];
    
      for (r = 0; r < nr; r++)
      {
        [subs set :r :[text get :_regs.start[r] :_regs.end[r]-1]];
      }
      
      [text free];
    }
  }
  else
    WARNING(DW_INVALID_ARG, "data");
  
  return subs;
}

@end

#endif

/*==========================================================================*/
